package Parser;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import DataModel.Feature;
import DataModel.FeatureBucket;
import DataModel.LociHolder;
import DataModel.Locus;
import DataModel.Tupel;
import Interfaces.SortableElements;
import SortSearch.QuickSorter;

/**
 * This class implements the parser of an GenBank file and creates a
 * {@link LociHolder}. If its a multi gbk file the {@link LociHolder} contains
 * several {@link Locus} in its list.
 * 
 * @author Stefan Lorenz<br>
 * <br>
 *         26.11.2012
 */
public class ParseGenBank {

	private LociHolder lociHolder;

	/**
	 * Contructor of the parser for genbank file
	 * 
	 * @param genBankFile
	 *            file to parse
	 * @throws IOException
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public ParseGenBank(File genBankFile, String reference, String range) throws IOException {
		Tupel positions = getRangeToParseFromString(range);
		if(positions.getStart() == -1 && positions.getStop() == -1){
			positions.setStart(Integer.MIN_VALUE);
			positions.setStop(Integer.MAX_VALUE);
		}
		BufferedReader reader = new BufferedReader(new FileReader(genBankFile));
		// repeat region or other features with no name
		int noNameCounter = 1;
		// create an empty genome
		this.lociHolder = new LociHolder();
		this.lociHolder.setFile(genBankFile);
		// is the actual read line of the embl file
		String currentLine = "";

		// empty locus to fill
		Locus locus = new Locus();

		// seenFeaturesyet
		boolean isFeature = false;
		// seen sequence
		boolean isSequence = false;
		// actual seen Feature
		Feature feature = null;
		// read genbankFile
		while ((currentLine = reader.readLine()) != null) {
			if (currentLine.startsWith("LOCUS")) {
				locus = readLocus(currentLine);
			} else if (currentLine.startsWith("SOURCE")) {
				locus.setNameOfOrganism(currentLine.substring(12));
			} else if (currentLine.startsWith("FEATURES") && locus.getLocusName().equals(reference)) {
				isFeature = true;
			} else if ((currentLine.startsWith("ORIGIN") || currentLine.startsWith("CONTIG")) && locus.getLocusName().equals(reference)) {

				// add last feature
				if (feature != null) {
					feature.getNameOfAdditionalInfo();
					if (feature.getName().equals("")) {
						feature.setName("No_Name " + noNameCounter);
						noNameCounter++;
					}

					boolean isIn = false;
					for (int i = 0; i < locus.getBucketList().size(); i++) {
						if (locus.getBucketList().get(i).addNewComponent(feature)) {
							isIn = true;
							break;
						}
					}
					if (!isIn) {
						locus.getBucketList().add(new FeatureBucket(feature));
					}
				}

				isSequence = true;
				isFeature = false;

				// read sequence
				StringBuffer seq = new StringBuffer();
				boolean tooMuch = false;

				while (!(currentLine = reader.readLine()).contains("//")) {

					if (!tooMuch) {
						for (int i = 0; i < currentLine.length(); i++) {
							char letter = currentLine.charAt(i);
							if ((!Character.isDigit(letter)) && (letter != ' ') && (letter != '\n')) {
								try {
									seq.append(Character.toUpperCase(letter));
								} catch (OutOfMemoryError e2) {
									tooMuch = true;
									System.err.println("Sequence is too big....");
									break;
								}
							}
						}
					}
				}
				if (positions.getStart() == Integer.MIN_VALUE && positions.getStop() == Integer.MAX_VALUE)
					locus.setSequence(seq);
				else
					locus.setSequence(new StringBuffer(seq.substring(positions.getStart(), positions.getStop())));

				// Sort Features
				FeatureBucket[] tempFeature = new FeatureBucket[locus.getBucketList().size()];
				for (int i = 0; i < locus.getBucketList().size(); i++) {
					tempFeature[i] = locus.getBucketList().get(i);
				}

				if (locus.getBucketList().size() > 2) {
					QuickSorter sorter = new QuickSorter(tempFeature);
					sorter.run();
					// XXX
					locus.setFeatureBucket(new ArrayList(Arrays.asList(sorter.getA())));
					sorter = null;
				}
				lociHolder.getListOfLoci().add(locus);
				// seenFeaturesyet
				isFeature = false;
				// seen sequence
				isSequence = false;
			}
			// read feature + postion
			else if (isFeature && !isSequence) {
				if (!currentLine.startsWith("                     ")) {
					// add feature to locus
					if (feature != null) {
						feature.getNameOfAdditionalInfo();
						if (feature.getName().equals("")) {
							feature.setName("No Name " + noNameCounter);
							noNameCounter++;
						}
						boolean isIn = false;
						for (int i = 0; i < locus.getBucketList().size(); i++) {
							if (locus.getBucketList().get(i).addNewComponent(feature)) {
								isIn = true;
								break;
							}
						}
						if (!isIn && !feature.getType().contains("source") && feature.getStart() >= positions.getStart() && feature.getStart() <= positions.getStop()) {
							locus.getBucketList().add(new FeatureBucket(feature));
						}
					}
					feature = readFeature(reader, currentLine);
					feature.setLocus(locus.getLocusName());
				}
				// read additional information
				else if (currentLine.substring(21, 22).equals("/")) {
					// && actualLine.contains("=\"")) {
					feature.getAdditionalInformation().add(readAdditionals(reader, currentLine, feature));
				}
			}
		}
		this.lociHolder.setTypeOfFile("gbk");
	}

	/**
	 * Adds additional information to the feature like dbxref, locus_tag etc.
	 * 
	 * @param reader
	 *            Reader to read file
	 * @param currentLine
	 *            current line to reads
	 * @param feature
	 *            Feature to add additional info to
	 * @throws IOException
	 */
	private String readAdditionals(BufferedReader reader, String currentLine, Feature feature) throws IOException {
		boolean next = true;
		String info = "";
		while (next) {
			if (currentLine.contains("/codon_start=") | currentLine.contains("/transl_table=") | currentLine.contains("/pseudo") | currentLine.contains("/number=") | currentLine.contains("/focus") | currentLine.contains("/rearranged")
					| currentLine.contains("/trans_splicing") | currentLine.contains("/citation") | currentLine.contains("/estimated_length=") | currentLine.contains("/rpt_type=") | currentLine.contains("/anticodon=")
					| currentLine.contains("/environmental_sample")) {

				return currentLine.substring(21);
			}
			if (currentLine.endsWith("\"")) {
				info = info.concat(currentLine.substring(21));
				next = false;
				return info;
			} else {
				if (!currentLine.endsWith("\"")) {
					info = "\n" + info.concat(currentLine.substring(21));

					while (!currentLine.endsWith("\"")) {
						currentLine = reader.readLine();
						info = "\n" + info.concat(currentLine.substring(21));
					}
					return info;
				}
			}
		}
		return null;
	}

	/**
	 * Parses feature line like CDS mRNA gene
	 * 
	 * @param reader
	 *            Reader to read file
	 * @param currentLine
	 *            current line to read
	 * @return the parsed feature
	 * @throws IOException
	 */
	private Feature readFeature(BufferedReader reader, String currentLine) {

		Feature feature = new Feature();
		StringTokenizer st = new StringTokenizer(currentLine);

		feature.setType(st.nextToken().toString());
		// strand information
		if (currentLine.contains("complement"))
			feature.setStrand("-");
		else
			feature.setStrand("+");

		// get positions
		boolean nextLine = true;
		// position
		while (nextLine) {
			// position
			String regex = "([\\d]*)([\\.][\\.][\\>]|[\\.][\\.])([\\d]*)";

			Pattern p = Pattern.compile(regex);
			Matcher m = p.matcher(currentLine);
			// suche exon positionen
			while (m.find()) {
				feature.getListOfPositions().add(new Tupel(Integer.valueOf(m.group(1)), Integer.valueOf(m.group(3))));
			}
			if (currentLine.endsWith(",")) {
				try {
					currentLine = reader.readLine();
				} catch (IOException e) {
					System.err.println(currentLine + " cant parse please check...");
				}
			} else {
				nextLine = false;
			}
		}
		// #exon with only one position
		if (feature.getListOfPositions().size() == 0) {
			String regex = "([\\d]+)";

			Pattern p = Pattern.compile(regex);
			Matcher m = p.matcher(currentLine);
			m.find();
			feature.getListOfPositions().add(new Tupel(Integer.valueOf(m.group(1)), Integer.valueOf(m.group(1))));
		}

		// set absoulte psotion of feature
		int absStart = feature.getListOfPositions().get(0).getStart();
		int absStop = feature.getListOfPositions().get(feature.getListOfPositions().size() - 1).getStop();
		feature.setAbsoultePositions(new Tupel(absStart, absStop));

		Tupel[] tmpTupel = new Tupel[feature.getListOfPositions().size()];
		for (int i = 0; i < tmpTupel.length; i++) {
			tmpTupel[i] = feature.getListOfPositions().get(i);
		}

		QuickSorter sorter = new QuickSorter((SortableElements[]) tmpTupel);
		sorter.run();
		ArrayList<Tupel> list = new ArrayList<Tupel>();

		for (SortableElements s : sorter.getA()) {
			list.add((Tupel) s);
		}

		return feature;
	}

	/**
	 * Reads line with "LOCUS"-Tag
	 * 
	 * @param currentLine
	 *            current line to read
	 * @return
	 */
	private Locus readLocus(String currentLine) {
		Locus locus = new Locus();
		StringTokenizer st = new StringTokenizer(currentLine);
		// LOCUS Tag
		st.nextToken();
		locus.setLocusName(st.nextToken().toString());
		locus.setLocusSize(Integer.valueOf(st.nextToken()));
		st.nextToken();
		locus.setTypeOfSequence(st.nextToken().toString());
		return locus;
	}

	/**
	 * Returns the parsed {@link LociHolder}
	 * 
	 * @return the loci holder
	 */
	public LociHolder getLociHolder() {
		return lociHolder;
	}

	/**
	 * parses an region string which must contain either "-" or ":" as seperator
	 * of two INTs
	 * 
	 * @param region
	 *            to load into browser
	 * @return
	 */
	private Tupel getRangeToParseFromString(String region) {
		String range = region.replace(" ", "");
		Tupel tupel = new Tupel(-1, -1);
		if (range.toLowerCase() != "complete") {
			// sort order
			int start = -1;
			int stop = -1;
			if (range.contains("-")) {
				start = Integer.parseInt(range.split("-")[0]);
				stop = Integer.parseInt(range.split("-")[1]);
			} else if (range.contains(":")) {
				start = Integer.parseInt(range.split(":")[0]);
				stop = Integer.parseInt(range.split(":")[1]);
			}
			tupel.setStart(Math.min(start, stop));
			tupel.setStop(Math.max(start, stop));
		}
		return tupel;
	}
}
